package com.safframework.server.core

import com.safframework.server.core.converter.ConverterManager
import com.safframework.server.core.handler.http.NettyHttpServerInitializer
import com.safframework.server.core.handler.socket.NettySocketServerInitializer
import com.safframework.server.core.handler.socket.WebSocketListener
import com.safframework.server.core.http.HttpMethod
import com.safframework.server.core.http.filter.HttpFilter
import com.safframework.server.core.log.LogManager
import com.safframework.server.core.router.RouteTable
import com.safframework.server.core.ssl.SslContextFactory
import io.netty.bootstrap.ServerBootstrap
import io.netty.channel.ChannelFuture
import io.netty.channel.ChannelInitializer
import io.netty.channel.ChannelOption
import io.netty.channel.EventLoopGroup
import io.netty.channel.nio.NioEventLoopGroup
import io.netty.channel.socket.SocketChannel
import io.netty.channel.socket.nio.NioServerSocketChannel
import io.netty.handler.ssl.SslContext
import java.net.UnknownHostException

/**
 *
 * @FileName:
 *          com.safframework.server.core.AndroidServer
 * @author: Tony Shen
 * @date: 2020-03-21 17:54
 * @version: V1.0 <描述当前版本功能>
 */
class TcpServer constructor(private val builder: ServerBuilder) : Server {

    private var channelFuture: ChannelFuture? = null
    private val routeRegistry: RouteTable = RouteTable
    private var sslContext: SslContext? = null
    private var webSocketPath: String? = null
    private var listener: WebSocketListener<Any>? = null

    private lateinit var bossGroup: EventLoopGroup
    private lateinit var workerGroup: EventLoopGroup
    private lateinit var channelInitializer: ChannelInitializer<SocketChannel>

    init {
        builder.errorController?.let { routeRegistry.errorController(it) } // 支持 RouteTable 中添加自定义的 errorController

        builder.logProxy?.let { LogManager.logProxy(it) }

        builder.converter?.let { ConverterManager.converter(it) }

        if (builder.useTls) {
            sslContext = SslContextFactory.createSslContext()
        }
    }

    override fun start() {

        channelInitializer = if (routeRegistry.isNotEmpty() && listener == null) {
            NettyHttpServerInitializer(routeRegistry, sslContext, builder)
        } else if (routeRegistry.isEmpty() && listener != null) {
            NettySocketServerInitializer(webSocketPath ?: "", listener!!)
        } else {
            LogManager.e(TAG, "channelInitializer is failed")
            return
        }

        object : Thread() {
            override fun run() {

                val bootstrap = ServerBootstrap()
                bossGroup = NioEventLoopGroup(1)
                workerGroup = NioEventLoopGroup(0)
                try {
                    bootstrap
                        .group(bossGroup, workerGroup)
                        .channel(NioServerSocketChannel::class.java)
                        .localAddress(builder.address, builder.port)
                        .childOption(ChannelOption.SO_KEEPALIVE, true)
                        .childOption(ChannelOption.SO_REUSEADDR, true)
                        .childOption(ChannelOption.TCP_NODELAY, true)
                        .childHandler(channelInitializer)
                    val cf = bootstrap.bind()
                    channelFuture = cf
                    cf.sync()
                    cf.channel().closeFuture().sync()
                } catch (e: UnknownHostException) {
                    throw RuntimeException(e)
                } catch (e: InterruptedException) {
                    throw RuntimeException(e)
                } finally {
                    close()
                }
            }
        }.start()
    }

    override fun request(
        method: HttpMethod,
        route: String,
        handler: RequestHandler
    ): TcpServer {
        routeRegistry.registerHandler(method, route, handler)
        return this
    }

    override fun filter(route: String, httpFilter: HttpFilter): Server {
        routeRegistry.addFilter(route, httpFilter)
        return this
    }

    override fun webSocket(webSocketPath: String?, listener: WebSocketListener<Any>): TcpServer {
        this.webSocketPath = webSocketPath
        this.listener = listener
        return this
    }

    private fun isWorkerGroupInitialized() = ::workerGroup.isInitialized

    private fun isBossGroupInitialized() = ::bossGroup.isInitialized

    override fun close() {
        try {
            channelFuture?.channel()?.close()

            if (isWorkerGroupInitialized()) {
                workerGroup.shutdownGracefully()
            }

            if (isBossGroupInitialized()) {
                bossGroup.shutdownGracefully()
            }
        } catch (e: InterruptedException) {
            LogManager.e(TAG, e.message ?: "error")
            throw RuntimeException(e)
        }
    }

    companion object {
        private const val TAG = "TcpServer"
    }
}